package drds.server.config.classloader;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * used for 's catlet class loader ,catlet's class file is stored in
 * _home/catlet dir
 */
public class DynamicLoad {
	private static final Logger LOGGER = LoggerFactory.getLogger("DynaClassLoader");
	/** key- class full name */
	private static Map<String, DynamicClass> dynamicClassMap = new ConcurrentHashMap<String, DynamicClass>();
	/** 需要加载了类的路径 */
	private final String classPath;
	private final DynamicClassLoader dynamicClassLoader;
	private final long classCheckMilis;

	public DynamicLoad(String classPath, int classCheckSeconds) {
		super();
		this.classPath = classPath;
		classCheckMilis = classCheckSeconds * 1000L;
		dynamicClassLoader = new DynamicClassLoader();
		LOGGER.info("dyna class load from " + classPath + ",and auto check for class file modified every " + classCheckSeconds + " seconds");
	}

	public Object getInstanceofClass(String className) throws Exception {
		DynamicClass dynamicClass = dynamicClassMap.get(className);
		boolean needReload = (dynamicClass == null || (dynamicClass.needReloadClass(classCheckMilis) && needReload(dynamicClass)));
		Class<?> newClass = null;
		if (needReload) {
			newClass = dynamicClassLoader.loadClass(className);
			dynamicClass = dynamicClassMap.get(className);
		} else {
			newClass = dynamicClass.$class;
		}

		if (dynamicClass != null) {
			Object classObject = dynamicClass.classObject;
			if (classObject == null) {
				classObject = dynamicClass.$class.newInstance();
				dynamicClass.classObject = classObject;
			}
			return classObject;
		} else {
			return newClass.newInstance();
		}
	}

	/**
	 * 加载某个类的字节码
	 */
	private static byte[] loadClassBytes(String path) throws IOException {
		BufferedInputStream bufferedInputStream = null;
		try {
			bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
			byte[] readed = new byte[1024 * 4];
			ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
			int count = 0;
			while ((count = bufferedInputStream.read(readed)) != -1) {
				byteArrayOutputStream.write(readed, 0, count);
			}
			return byteArrayOutputStream.toByteArray();
		} finally {
			if (bufferedInputStream != null) {
				bufferedInputStream.close();
			}
		}
	}

	private boolean needReload(DynamicClass dynamicClass) throws IOException {
		boolean needReload = false;
		File file = new File(dynamicClass.filePath);
		if (file.exists()) {
			long newTime = file.lastModified();
			long oldTime = dynamicClass.lastModified;
			if (oldTime != newTime) {
				// need reload
				dynamicClass.lastModified = newTime;
				dynamicClass.classObject = null;
				dynamicClass.$class = null;
				needReload = true;
			}
		}
		return needReload;
	}

	class DynamicClassLoader extends ClassLoader {
		public DynamicClassLoader() {
		}

		public DynamicClassLoader(ClassLoader parentLoader) {
			super(parentLoader);
		}

		/**
		 * @param classFullName
		 */
		public Class<?> loadClass(String classFullName) throws ClassNotFoundException {
			if (classFullName.startsWith("java") || classFullName.startsWith("sun") || classFullName.startsWith("org.opencloudb")) {
				return super.loadClass(classFullName);
			}
			DynamicClass dynamicClass = dynamicClassMap.get(classFullName);
			if (dynamicClass != null) {
				if (dynamicClass.$class != null) {
					return dynamicClass.$class;
				}
			} else {
				try {
					dynamicClass = searchFile(classPath, classFullName);
				} catch (Exception e) {
					LOGGER.error("SearchFileError", e);
				}
			}

			if (dynamicClass == null) {
				return super.loadClass(classFullName);
			} else {
				LOGGER.info("load class from file " + dynamicClass.filePath);
				Class<?> $class = null;
				if (dynamicClass.isJar) {
					$class = dynamicClass.$class;
				} else {
					byte[] content;
					try {
						content = loadClassBytes(dynamicClass.filePath);
					} catch (IOException e) {
						throw new ClassNotFoundException(e.toString());
					}
					$class = super.defineClass(classFullName, content, 0, content.length);
					dynamicClass.$class = $class;
				}
				dynamicClass.classObject = null;
				dynamicClassMap.put(classFullName, dynamicClass);
				return $class;
			}

		}

		private DynamicClass searchFile(String classpath, String fileName) throws Exception {
			DynamicClass dynamicClass = null;
			String path = fileName.replace('.', File.separatorChar) + ".class";
			System.out.println("class " + classpath + " file " + path);
			File file = new File(classpath, path);
			if (file.isFile()) {
				String filePath = file.getPath();
				System.out.println("found " + filePath);

				dynamicClass = new DynamicClass(file.getPath());
				dynamicClass.lastModified = file.lastModified();
				return dynamicClass;
			} else {
				path = fileName.replace('.', File.separatorChar) + ".jar";
				System.out.println("jar " + classpath + " file " + path);
				file = new File(classpath, path);
				if (file.isFile()) {
					try {
						dynamicClass = new DynamicClass(file.getPath());
						dynamicClass.lastModified = file.lastModified();
						dynamicClass.$class = JarLoader.loadJar(classpath + "/" + path, fileName);
						dynamicClass.isJar = true;
						return dynamicClass;
					} catch (Exception err) {
						return null;
					}

				}
				return null;
			}

		}

	}

	public void clearUnUsedClass() {
		long deadTime = System.currentTimeMillis() - 30 * 60 * 1000L;
		Iterator<Map.Entry<String, DynamicClass>> iterator = dynamicClassMap.entrySet().iterator();
		while (iterator.hasNext()) {
			Map.Entry<String, DynamicClass> entry = iterator.next();
			DynamicClass dynamicClass = entry.getValue();
			if (dynamicClass.lastModified < deadTime) {
				LOGGER.info("clear unused catlet " + entry.getKey());
				dynamicClass.clear();
				iterator.remove();
			}
		}
	}
}

class DynamicClass {
	public final String filePath;
	public volatile long lastModified;
	public Class<?> $class;
	public Object classObject;
	public boolean isJar = false;

	public boolean needReloadClass(long classCheckMilis) {
		if (lastModified + classCheckMilis < System.currentTimeMillis()) {
			return true;
		} else {
			return false;
		}
	}

	public void clear() {
		this.$class = null;
		this.classObject = null;

	}

	public DynamicClass(String filePath) {
		super();
		this.filePath = filePath;
	}
}
